<?php

require_lib('net/Snoopy.class',false);

/** 
 * 传送淘宝接口[简称taobao]API参数到网店,得到相应返回值。
 * @author alex  
 * @version v1.0  2010-5-30
 */
class ECSHOPRequest
{
	/**
	 * @var string $host_url taobao API的网站地址
	 */
	var $_host_url = '';
	/**
	 * @var string $_sandbox_url taobao 沙箱测试环境的网站地址
	 */
	var $_sandbox_url = 'http://gw.api.tbsandbox.com/router/rest';
	
	var $_end_url='/api/ecshop_api/';
	/**
	 * @var array $app_token 应用app访问taobao的令牌，参数包括：<br/>
	 * string app_key : 必须，  API应用的标志符.<br/>
	 * string app_secret :必须， 用户登录appSecret，用于用户鉴权，将相关参数通过md5,hmac加密为一个sign值<br/>
	 * string app_session   :可选，淘宝会话，需要会话的接口必须提供 
	 */
	var $app_token = array ();
	
	/**
	 * @var array $app_setting 应用app访问taobao的通用参数设置，一般不改变默认值，包括：<br/>
	 * string format：响应格式。xml,json，默认是 'json'。<br/>
	 * strng  sign_method :签名方法，md5,hmac，默认是 'md5'。<br/>
	 * string v：API接口版本号，2.0<br/>
	 * strng  timestamp:时间戳，格式为yyyy-mm-dd hh:mm:ss，例如：2008-01-25 20:23:30。淘宝API服务端允许客户端请求时间误差为10分钟<br/>
	 */
	var $app_setting = array ('format' => 'json', //json , xml
'sign_method' => 'hmac', // md5 , hmac
'v' => '2.0', 'timestamp' => '2010-06-01 12:00:00' );
	
	/**
	 * @var string $url 上次调用API接口的URL.
	 */
	var $url;
	
	/**
	 * @var bool $isRunInSandBox 是否在沙箱测试环境下运行，默认true
	 */
	var $isRunInSandBox = true;
	
	/**
	 * @var int HTTPMETHOD_POST $httpmethod使用POST方法传递API参数，$httpmethod默认使用HTTPMETHOD_POST
	 */
	const HTTPMETHOD_POST = 0;
	/**
	 * @var int HTTPMETHOD_POST $httpmethod使用GET方法传递API参数
	 */
	const HTTPMETHOD_GET = 1;
	/**
	 * @var int HTTPMETHOD_POSTWITHFILE $httpmethod使用POST方法传递API参数，并且还要上传文件<br/>
	 * 如上传图像，array('image' => '/tmp/cs.jpg')
	 */
	const HTTPMETHOD_POSTWITHFILE = 2;
	/**
	 * 
	 * @var int $httpmethod 传递API参数的方法，其值是：@see HTTPMETHOD_POST,@see HTTPMETHOD_GET,@see HTTPMETHOD_POSTWITHFILE <br/>
	 * 默认为HTTPMETHOD_POST，如果参数不合法，使用HTTPMETHOD_POST。
	 */
	var $httpmethod = 0;
	/**
	 * @var array $postWithFiles 当$httpmethod=HTTPMETHOD_POSTWITHFILE时，使用此参数设置的文件上传<br/>
	 * 如上传图像，array('image' => '/tmp/cs.jpg')
	 */
	var $postWithFiles = array ();
	
	function __construct()
	{
		$time_zone = @date_default_timezone_get ();
		if (! $time_zone || $time_zone == 'UTC')
			date_default_timezone_set ( 'Asia/Shanghai' );
		$this->httpmethod = self::HTTPMETHOD_POST;
	}
	
	/**
	 * 得到taobo API的返回值，JSON,XML字符串。
	 * @param string $cmdid 要调用的api接口的cmdid字符串，如user.getUserInfo，如果为空，将返回false.
	 * @param array $paramArr 要调用的api接口的参数数组，一般通用的参数不要传，如charset，format等。
	 * @return string|bool 调用的api接口得到的JSON,XML字符串。
	 */
	function &get($cmdid, array $paramArr)
	{
		
		if (! $cmdid)
			return false; //count($paramArr)==0,some function is empty
		if (! $this->app_token ['app_key'] || ! $this->app_token ['app_secret'])
			return false;
		$this->app_setting ['timestamp'] = date ( 'Y-m-d H:i:s' );
		$host_url = $this->isRunInSandBox ? $this->_sandbox_url : $this->_host_url;
		
		//组装url,param array.
		$paramArr = $paramArr + $this->app_setting;
		$paramArr ['app_key'] = $this->app_token ['app_key'];
		/*
		if ($this->app_token ['app_session'])
			$paramArr ['session'] = $this->app_token ['app_session'];
			*/
		$paramArr ['method'] = $cmdid;
		$paramArr ['sign'] = $this->_createSign ( $paramArr );
		
		$this->url = $host_url;
		
		//call taobao method
		$snoopy = new Snoopy ();
		if ($this->app_setting ['format'] == 'xml')
			$snoopy->accept = 'text/xml';
		else
			$snoopy->accept = 'text/json';
		
		if ($this->httpmethod == self::HTTPMETHOD_POST)
			$snoopy->submit ( $this->url, $this->_prepareParam ( $paramArr ) );
		elseif ($this->httpmethod == self::HTTPMETHOD_GET)
		{
			$this->url .= '?' . $this->_createStrParam ( $paramArr );
			$snoopy->fetch ( $this->url );
		}
		elseif ($this->httpmethod == self::HTTPMETHOD_POSTWITHFILE && count ( $this->postWithFiles ) > 0)
		{
			$snoopy->_submit_type = 'multipart/form-data';
			$snoopy->submit ( $this->url, $this->_prepareParam ( $paramArr ), $this->postWithFiles );
		}
		else
			$snoopy->submit ( $this->url, $this->_prepareParam ( $paramArr ) );
		
		
	//	print_r($paramArr);exit;
	//	print_r($snoopy->results);exit;
		if ($snoopy->error)
			throw new Exception ( $snoopy->error );
		return $snoopy->results;
	}
	
	/**
	 * 得到 API的返回值，JSON,XML生成的数组。
	 * @param string $cmdid 要调用的api接口的cmdid字符串，如果为空，将返回false.
	 * @param array $paramArr 要调用的api接口的参数数组，一般通用的参数不要传，如charset，format等。
	 * @return array|bool 调用的api接口得到的 JSON,XML生成的数组。
	 */
	function &getArray($cmdid, array $paramArr)
	{
		//parse xml format
		if ($this->app_setting ['format'] == 'xml')
		{
			$result = &$this->get ( $cmdid, $paramArr );
			$arr = array ();
			if (! $this->parseXmlString ( $result, $arr ))
				return false;
			return $arr;
		}
		
		//parse json format
		if (! ($result = &$this->get ( $cmdid, $paramArr )))
			return false;
		
		
		$result = preg_replace ( '/(":)(\d{9,})/i', '${1}"${2}"', $result ); //taobao bug,number >2^32
		$arr = json_decode ( $result, true );
		if ($arr == NULL)
		{
			$patterns = array ('/,+\s*\}/', '/,+\s*\]/', '/"\s+|\s+"/', '/\n|\r/' );
			$replacements = array ('}', ']', '"', ' ' );
			$result = preg_replace ( $patterns, $replacements, $result );
			$arr = json_decode ( $result, true );
		}
	//	print_r($arr);exit;
		return $arr;
	}
	
	/**
	 * @deprecated 得到API的返回值，JSON生成的stdclass对象。
	 * @param string $cmdid  要调用的api接口的cmdid字符串，如user.getUserInfo，如果为空，将返回false.
	 * @param array $paramArr 要调用的api接口的参数数组，一般通用的参数不要传，如charset，format等。
	 * @return object|bool 调用的api接口得到的JSON生成的stdclass对象。
	 */
	function getObj($cmdid, array $paramArr)
	{
		if (! ($result = &$this->get ( $cmdid, $paramArr )))
			return false;
		return json_decode ( $result, false );
	}
	/**
	 * 检查接口调用get*函数返回是否有错误
	 * @param  mixed $result  接口调用get*函数返回值.
	 * @return bool true 接口调用有效，false 接口调用出错.
	 */
	function checkResult(&$result, &$errorCode, &$errorMsg)
	{
		$errorCode = 0;
		$errorMsg = '';
		if (is_array ( $result ))
		{
			if (! array_key_exists ( 'error_response', $result ))
				return true;
			else
			{
				$error = $result ['error_response'];
				$errorCode = $error ['code'];
				$errorMsg = $error ['msg'];
				if (array_key_exists ( 'sub_code', $error ))
					$errorMsg .= ";\n" . $error ['sub_code'];
				if (array_key_exists ( 'sub_msg', $error ))
					$errorMsg .= ";\n" . $error ['sub_msg'];
				
			}
		}
		elseif (is_object ( $result ))
		{
			if (! property_exists ( $result, 'error_response' ))
				return true;
			else
			{
				$error = $result->error_response;
				$errorCode = $error->code;
				$errorMsg = $error->msg;
				if (property_exists ( $error, 'sub_code' ))
					$errorMsg .= ";\n" . $error->sub_code;
				if (property_exists ( $error, 'sub_msg' ))
					$errorMsg .= ";\n" . $error->sub_msg;
				
			}
		}
		elseif (is_string ( $result ))
		{
			$match = array ();
			if (! preg_match ( '/^\{"error_response":\{.+,"code":(\d*),"msg":"(.+)\}\}$/', $result, $match ))
				return true;
			else
			{
				$errorCode = $match [1];
				$submatch = array ();
				if (preg_match ( '/(.+)","sub_code":"(.*)","sub_msg":"(.*)"/', $match [2], $submatch )) //sub_code and sun_msg all
					$errorMsg = $submatch [1] . ";\n" . $submatch [2] . ";\n" . $submatch [3];
				else
					$errorMsg = rtrim ( $match [2], '"' );
			
			}
		}
		elseif (is_bool ( $result ))
			return $result;
		
		return false;
	}
	
	/*
	 * 组参函数 ,内部用
	 */
	private function _createStrParam(array &$paramArr)
	{
		$strParam = '';
		foreach ( $paramArr as $key => $val )
		{
			if ($key != '' && isset ( $val ))
			{
				if (is_array ( $val ))
				{
					if (count ( $val ) > 0)
						foreach ( $val as $item )
							$strParam .= $key . '=' . urlencode ( $item ) . '&';
				}
				else
					$strParam .= $key . '=' . urlencode ( $val ) . '&';
			}
		}
		return $strParam;
	}
	private function _prepareParam(array &$paramArr)
	{ //trim empty value
		$Paramok = array ();
		foreach ( $paramArr as $key => $val )
		{
			if ($key != '' && isset ( $val ))
			{
				if (is_array ( $val ))
				{
					$itemarr = array ();
					foreach ( $val as $item )
						if ($item != '')
							$itemarr [] = $item;
					$Paramok [$key] = $itemarr;
				}
				else
					$Paramok [$key] = $val; //Snoopy类在执行submit函数时，它自动会将参数做urlencode编码
			}
		}
		

		
		return $Paramok;
	}
	/*
	 * 签名函数,内部用 
	 */
	private function _createSign(array $paramArr)
	{
		$sign = '';
		ksort ( $paramArr );
		foreach ( $paramArr as $key => $val )
		{
			if ($key != '' && isset ( $val ))
			{
				if (is_array ( $val ))
				{
					if (count ( $val ) > 0)
						foreach ( $val as $item )
							$sign .= $key . $item;
				}
				else
					$sign .= $key . $val;
			}
		}
		
		return $sign=$app_secret = $this->app_token ['app_secret'];
		
		if ($this->app_setting ['sign_method'] === 'hmac')
		{
			$sign = strtoupper ( hash_hmac ( 'md5', $sign, $app_secret ) );
		}
		else
		{
			$sign = strtoupper ( md5 ( $app_secret . $sign . $app_secret ) );
		}
		
		
		return $sign;
	}
	/**
	 * xml字符串解析为数组，xml字符串如<orders><order orderid="97913707" merchantid="6619" 
	 * paysum="32.90" isdeleted="0" ispaid="0" status="1" merchantorderid="" /></orders>
	 * <br/>如果不是xml字符串，不解析。<br/>原如果解析失败，抛出Execption异常
	 * @param string $xmlString xml字符串
	 * @param array $resultarr 解析后的结果数组
	 */
	function parseXmlString(&$xmlString, array &$arr)
	{
		$xmlString = trim ( $xmlString );
		if (strpos ( $xmlString, '<' ) !== 0)
			return false; //simple test is xml
		try
		{
			$root = new SimpleXMLElement ( $xmlString );
			$resultarr = array ();
			$this->__fillArray ( $root, $resultarr );
			if (strpos ( $xmlString, '<error_response>' ) === false)
				$arr ['response'] = $resultarr; //set taobao return val form
			else
				$arr ['error_response'] = $resultarr; //set taobao error
			return true;
		}
		catch ( Exception $e )
		{
			throw new Exception ( '返回xml解析错误:' . $e->getMessage () );
		}
	}
	//----private function ------//
	function __fillArray($root, array &$resultarr)
	{
		if (! $root)
			return;
		$rootarr = get_object_vars ( $root );
		$isList = false;
		foreach ( $rootarr as $key => $val )
		{
			if ($key == '@attributes')
			{
				$isList = $val ['list']; //is array
			}
			else if (is_object ( $val ))
			{
				$newarr = array ();
				$this->__fillArray ( $val, $newarr );
				if ($isList)
					$resultarr [$key] = array ($newarr ); //one element array
				else
					$resultarr [$key] = $newarr;
			}
			else if (is_array ( $val ) && count ( $val ) > 0)
			{ //is array
				$valarr = array ();
				for($i = 0; $i < count ( $val ); $i ++)
				{
					$newarr = array ();
					$this->__fillArray ( $val [$i], $newarr );
					$valarr [] = $newarr;
				}
				$resultarr [$key] = $valarr;
			}
			else
				$resultarr [$key] = $val;
		}
	}

}

